package com.angel.architecture.flex.hibernate;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;

import org.hibernate.EntityMode;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;

import com.angel.architecture.exceptions.NonBusinessException;
import com.angel.architecture.flex.locator.ObjectLocator;
import com.angel.common.helpers.ReflectionHelper;

public class HibernateUtils {
    public static Class<?> getActualClass(Object instance) {
        SessionFactory sessionFactory = ObjectLocator.get(SessionFactory.class);
        return getActualClass(sessionFactory, instance);
    }

    public static Class<?> getActualClass(SessionFactory sessionFactory, Object instance) {
        if (instance instanceof HibernateProxy) {
            HibernateProxy proxy = (HibernateProxy) instance;
            return proxy.getHibernateLazyInitializer().getPersistentClass();
        }
        return instance.getClass();
    }

    public static Collection<Annotation> getActualAnnotations(Object fieldOwner, Field field) {
        if (fieldOwner instanceof HibernateProxy) {
            HibernateProxy proxy = (HibernateProxy) fieldOwner;
            Field actualField = null;
            try {
                actualField = proxy.getHibernateLazyInitializer().getPersistentClass().getDeclaredField(field.getName());
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
            return Arrays.asList(actualField.getAnnotations());
        }
        return Arrays.asList(field.getDeclaredAnnotations());
    }

    @SuppressWarnings("unchecked")
	public static Collection<Annotation> getActualAnnotations(Object methodOwner, Method method) {
        if (methodOwner instanceof HibernateProxy) {
            HibernateProxy proxy = (HibernateProxy) methodOwner;
            Method actualMethod = null;
            try {
                actualMethod = proxy.getHibernateLazyInitializer().getPersistentClass().getMethod(method.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
            return Arrays.asList(actualMethod.getDeclaredAnnotations());
        }
        return Arrays.asList(method.getAnnotations());
    }

    public static Object getFieldValue(Object instance, String propertyName) {
        if (instance instanceof HibernateProxy) {
            Object implementation = ((HibernateProxy) instance).getHibernateLazyInitializer().getImplementation();
            return ReflectionHelper.getFieldValue(implementation, propertyName);
        }
        return ReflectionHelper.getFieldValue(instance, propertyName);
    }

    public static void setFieldValue(Object instance, String propertyName, Object value) {
        if (instance instanceof HibernateProxy) {
            Object implementation = ((HibernateProxy) instance).getHibernateLazyInitializer().getImplementation();
            ReflectionHelper.setFieldValue(implementation, propertyName, value);
        }
        ReflectionHelper.setFieldValue(instance, propertyName, value);
    }

    public static String getIdPropertyName(SessionFactory sessionFactory, String className) {
        ClassMetadata classMetadata = sessionFactory.getClassMetadata(className);
        if (classMetadata == null) {
            return null;
        }
        return classMetadata.getIdentifierPropertyName();
    }

    public static Serializable getObjectId(SessionFactory sessionFactory, Object object) {
        if (object == null) {
            return null;
        }
        if (object instanceof HibernateProxy) {
            HibernateProxy proxy = (HibernateProxy) object;
            return proxy.getHibernateLazyInitializer().getIdentifier();
        }
        ClassMetadata classMetadata = sessionFactory.getClassMetadata(getActualClass(object));
        if (classMetadata == null) {
            return null;
        }
        return classMetadata.getIdentifier(object, EntityMode.POJO);
    }

    public static Serializable getObjectId(Object object) {
        return getObjectId((SessionFactory) ObjectLocator.get(SessionFactory.class), object);
    }

    public static void setObjectId(SessionFactory sessionFactory, Object object, Serializable id) {
        if (object == null) {
            return;
        }
        ClassMetadata classMetadata = sessionFactory.getClassMetadata(getActualClass(object));
        if (classMetadata == null) {
            throw new NonBusinessException("No puedo setear el id de un objeto porque su clase no es una entidad mapeada de hibernate");
        }
        classMetadata.setIdentifier(object, id, EntityMode.POJO);
    }

    public static void setObjectId(Object object, Serializable id) {
        setObjectId((SessionFactory) ObjectLocator.get(SessionFactory.class), object, id);
    }
}